Skip to content

feat(ui): [FEATURE BRANCH] Guarantee consuming-app global CSS can't distort Bible components - Shadow DOM - #348

Draft
cameronapak wants to merge 31 commits into
mainfrom
journey-to-the-shadow-dom
Draft

cameronapak wants to merge 31 commits into
mainfrom
journey-to-the-shadow-dom

Conversation

@cameronapak

@cameronapak cameronapak commented Aug 19, 2026 •

Copy link
Copy Markdown
Collaborator

This PR is in progress and will continue to be defined. This PR was created as a feature branch for https://lifechurch.atlassian.net/browse/YPE-4113.

Greptile Summary

The PR prototypes automatic Shadow DOM style isolation for YouVersionAuthButton and documents the resulting compatibility boundaries.

  • Adds a reusable shadow-root host with constructable-stylesheet and fallback style delivery.
  • Wraps the authentication button while preserving its public React props and forwarded ref.
  • Adds hostile-CSS demonstrations, browser stories, lifecycle tests, an ADR, and a coordinated major changeset.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains within the eligible follow-up-review scope.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/ui/src/lib/shadow-root-host.tsx Adds the shadow-root lifecycle, per-document constructed stylesheet cache, fallback style resource, host reset, and React portal.
packages/ui/src/lib/shadow-isolation.tsx Adds a typed higher-order wrapper that preserves component props and forwarded refs while introducing the isolation host.
packages/ui/src/components/YouVersionAuthButton.tsx Moves the existing authentication-button implementation behind automatic Shadow DOM isolation and pins its intended font utility.
packages/ui/src/styles/global.css Adds shadow-host pseudo-element suppression to the generated SDK stylesheet.
packages/ui/src/components/YouVersionAuthButton.shadow-isolation.stories.tsx Adds browser coverage for hostile global selectors, host pseudo-elements, and same-origin iframe stylesheet adoption.
packages/ui/src/lib/shadow-root-host.test.tsx Adds focused unit coverage for Strict Mode attachment, host resets, and fallback stylesheet identity.
docs/adr/0005-prototype-shadow-dom-style-isolation.md Documents the prototype architecture, compatibility impact, verified behavior, and deliberately deferred concerns.
.changeset/prototype-shadow-dom-isolation.md Records the prototype as a coordinated major release across the fixed-version SDK packages.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Consumer[Consumer application] --> Host[Light-DOM shadow host]
  Host --> Root[Open shadow root]
  Styles[Embedded SDK stylesheet] --> Root
  Root --> Portal[React portal]
  Portal --> Button[YouVersionAuthButton implementation]
  HostCSS[Consumer global CSS] -. selector boundary .-> Host
Loading

Reviews (3): Last reviewed commit: "Merge branch 'main' into journey-to-the-..." | Re-trigger Greptile

Context used (3)

* feat(ui)!: prototype automatic Shadow DOM isolation

* docs(examples): add hostile CSS POC demo

* docs(examples): clarify hostile CSS vectors

* docs(ui): align shadow DOM prototype ADR

* test(ui): strengthen shadow isolation proof

* refactor(ui): remove shadow isolation type assertions

* fix(ui): block hostile shadow host pseudo-elements

* docs(ui): clarify shadow isolation trade-offs

* test(ui): align shadow isolation demo conventions

* docs(examples): keep hostile demo labels readable

* fix(ui): use style precedence for shadow fallback stylesheet

Addresses PR review feedback: the non-adoptedStyleSheets fallback
rendered a bare <style> tag inside the shadow-root portal. Use
React 19's <style href precedence> resource form instead so React
hoists and de-duplicates the stylesheet within the shadow root
rather than risking duplicate insertion.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: abharms <aharms@works24.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
@cameronapak cameronapak self-assigned this Aug 19, 2026
@changeset-bot

changeset-bot Bot commented Aug 19, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8e53dd9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@youversion/platform-react-ui Major
@youversion/platform-core Major
@youversion/platform-react-hooks Major
vite-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cameronapak cameronapak reopened this Aug 25, 2026
@cameronapak
cameronapak marked this pull request as draft August 25, 2026 14:20
abharms and others added 17 commits August 25, 2026 12:45
* feat(ui): prototype shadow-local top-layer popovers

* fix(ui): reconcile shadow portal strategy changes

* fix(ui): narrow shadow portal spike scope

* fix(ui): harden shadow portal lifecycle

---------

Co-authored-by: abharms <aharms@works24.com>
* feat(ui): validate shadow DOM dialog top layer

* docs(ui): separate shadow DOM rollout plan

* refactor(ui): simplify shadow dialog coordination

* test(ui): prove shadow dialog escapes clipping

* refactor(ui): tighten shadow dialog portal behavior

---------

Co-authored-by: abharms <aharms@works24.com>
* fix(ui): harden Shadow DOM CSS isolation

* test(ui): cover InputGroup SDK radius token

* test(ui): align input group radius assertion

---------

Co-authored-by: abharms <aharms@works24.com>
* fix(ui): route verse actions through shadow portals

* style(ui): use logical overflow in shadow story

* fix(ui): address verse action popover review feedback

---------

Co-authored-by: abharms <aharms@works24.com>
* test(ui): validate shadow consumer compatibility (YPE-5436)

* test(ui): strengthen shadow compatibility evidence (YPE-5436)

* test(ui): await shadow content rendering (YPE-5436)

* docs(ui): qualify shadow automation guidance (YPE-5436)

* docs(ui): clarify shadow compatibility stories (YPE-5436)

* docs(ui): tighten shadow rollout research handoff (YPE-5436)

* test(ui): remove instructional copy assertions

* test(ui): address YPE-5436 review feedback

---------

Co-authored-by: abharms <aharms@works24.com>
)

* test(ui): prove nested overlays through the production shadow seam

Exercise YPE-5355 nested and concurrent overlay scenarios against the real ShadowRootHost + Radix path, without adding an ownership registry.

* fix(ui): restore shadow dialog focus after a rapid reopen

The production seam handled nested and concurrent overlays without a new ownership registry. Rapid reopen during exit captured the outgoing overlay as the restore target; skip portal-container nodes and fall back to the last wrapper control.

* test(ui): add static Storybook frames for the production overlay seam

Leave the ticket scenarios on screen so the production VerseActionPopover, permission dialog, nested popover, and independent overlays can be inspected without racing the play function.

* test(ui): finalize YPE-5355 overlay evidence

* docs: keep overlay proof evidence-only

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: abharms <aharms@works24.com>
* fix(ui): address shadow DOM review regressions

* test(ui): strengthen touch interaction coverage

* test(ui): wait for placement before measuring touch reanchoring

---------

Co-authored-by: abharms <aharms@works24.com>
* fix(ui): enforce custom property contract

* refactor(ui): clarify CSS contract validation

* docs(ui): clarify completed CSS audit step

* test(ui): strengthen custom property contract coverage

* refactor(ui): simplify custom property exceptions
* test(ui): prove shadow DOM hydration contract

* test(ui): strengthen shadow DOM hydration evidence

* docs: tighten shadow DOM hydration decision

* test(ui): prove iframe shadow styles apply
* test(ui): validate realistic Shadow DOM usage

* test(ui): prove Strict Mode effect replay

* fix(ui): make Strict Mode story environment-aware
* fix(ci): unblock journey pull requests

* fix(ci): scope stacked release previews

* test(ci): exercise stacked release preview behavior
@cameronapak cameronapak changed the title feat(ui): Guarantee consuming-app global CSS can't distort Bible components - Shadow DOM feat(ui): [FEATURE BRANCH] Guarantee consuming-app global CSS can't distort Bible components - Shadow DOM Sep 21, 2026
)

* fix(ui): restore focus after rapid shadow dialog reopen

YPE-5889

* fix(ui): reset prevented shadow focus restoration

YPE-5889

* chore: retrigger upstream checks

YPE-5889

* fix(ui): preserve document focus restoration

YPE-5889

* test(ui): prove cross-document focus rejection

YPE-5889

* test(ui): mock provider font stylesheet globally

* fix(ui): reject moved shadow focus targets

* docs: clarify pre-rollout shadow focus scope

* fix(ui): preserve shadow dialog focus ownership

* test(ui): cover overlay-only dialog reopen

* docs(ui): correct rapid reopen lifecycle
* test(ui): consolidate Storybook DOM wait helpers

* test(ui): preserve shadow root wait timing

* test(ui): mock font stylesheet in Storybook
* test(ui): add cross-browser shadow DOM coverage

* test(ui): stabilize Firefox story readiness

* fix(ci): stabilize shadow browser jobs

* test(ui): make overlay exit ordering deterministic

* test(ui): await Storybook assertions

* fix(ci): avoid Firefox matcher teardown rejections

* test(ui): await shadow browser assertions

* test(ui): stabilize shadow browser timing

* test(ui): consume shadow story wait promises

* test(ui): avoid matcher promises in shadow stories

* fix(ui): handle language sync rejections

* test(ui): log browser unhandled rejection details

* test(ui): ignore expected font load events in firefox

* test(ui): filter firefox stylesheet load events

* test(ui): ignore browser events in lifecycle assertion

* test(ui): narrow font rejection filter

* test(ui): harden shadow DOM cross-browser evidence

* fix(ui): preserve shadow picker spacing in Safari

* fix(ui): preserve document-root spacing in Safari

* test(ui): cover spacing fixes cross-browser

* test(ui): reuse Storybook wait helper
* docs(ui): define Shadow DOM rollout policy

Reconcile YPE-5356 research into a coordinated package-wide rollout plan and focused implementation tickets.

* docs(ui): simplify rollout policy ownership

Keep the production policy authoritative and remove stale or overly broad dependency language found during the YPE-5356 simplification audit.

* docs(ui): clarify pending rollout evidence

Complete the public provider inventory and distinguish YPE-5946 branch results from browser coverage already present on YPE-5356.

* docs(ui): reconcile Shadow DOM rollout evidence

* docs(ui): keep rollout policy authoritative

* docs(ui): correct rollout compatibility status
* feat(ui): harden shadow boundary foundation

Refs: YPE-5947

* test(ui): consolidate shadow stylesheet failures

Refs: YPE-5947

* test(ui): prove shadow recovery across documents

Refs: YPE-5947

* test(ui): assert document-scoped shadow stylesheet
…-dom

# Conflicts:
#	.github/workflows/major-release-signoff.yml
@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

🚨 Breaking change detected — signoff required for v3.0.0

This PR adds a changeset declaring a major bump, which would ship all three packages as v3.0.0 (they are a fixed group in .changeset/config.json, so they version together). Merging is blocked until a repo collaborator with write access comments on this PR with all four (4) of the following:

  1. The verbatim acknowledgment phrase below,
  2. The precise next version (v3.0.0 or 3.0.0),
  3. The full commit hash being approved (8e53dd9d450a83f66bc3c4a839172222563cfa4a), and
  4. A 🚀 (:rocket:) emoji.

I confirm that this is an intentional breaking change, and I have read the release procedures. I understand and have documented its impact upon release.

Copy-paste-ready reply:

I confirm that this is an intentional breaking change, and I have read the release procedures. I understand and have documented its impact upon release.

v3.0.0 8e53dd9d450a83f66bc3c4a839172222563cfa4a 🚀

The check re-runs automatically when a qualifying comment is posted or edited. Pushing new commits changes the hash, so a fresh signoff is needed.

abharms and others added 6 commits September 26, 2026 10:52
* feat(ui): harden shadow boundary foundation

Refs: YPE-5947

* test(ui): consolidate shadow stylesheet failures

Refs: YPE-5947

* test(ui): prove shadow recovery across documents

Refs: YPE-5947

* test(ui): assert document-scoped shadow stylesheet

* feat(ui): isolate Bible pickers in Shadow DOM (YPE-5949)

* test(ui): deduplicate picker portal evidence (YPE-5949)

* test(ui): strengthen picker boundary evidence (YPE-5949)

* test(ui): stabilize chapter picker search journey (YPE-5949)

* test(ui): keep picker search proof deterministic (YPE-5949)

* test(ui): await picker data before search (YPE-5949)

* test(ui): isolate picker journey data (YPE-5949)

* test(ui): preserve picker initial state coverage (YPE-5949)

* test(ui): avoid dynamic picker height assertion (YPE-5949)

* docs(ui): align picker rollout records (YPE-5949)

* docs(ui): include reader search in Shadow DOM rollout

* docs(ui): refresh Shadow DOM evidence guidance
* feat(ui): isolate leaf components (YPE-5948)

* test(ui): close leaf isolation review gaps

* chore(ui): account for leaf isolation bundle cost

* fix(ui): preserve leaf host and theme contracts

* fix(ui): preserve explicit shadow themes

* chore(ui): allow bundle measurement variance

* fix(ui): preserve isolated leaf borders

* docs(ui): tighten shadow rollout guidance

* test(ui): remove redundant avatar story plays

* fix(ui): inherit provider theme in footnotes

* fix(ui): address shadow isolation review feedback
The replacement-failure recovery test passed even when the SDK sheet was
cached before replaceSync, which would leave later roots unstyled. Assert
that the recovered root adopts a populated sheet.

Also explain why the picker search inputs use onInput and why two picker
stories clear search with fireEvent.input inside shadow roots.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(ui): isolate leaf components (YPE-5948)

* test(ui): close leaf isolation review gaps

* chore(ui): account for leaf isolation bundle cost

* fix(ui): preserve leaf host and theme contracts

* fix(ui): preserve explicit shadow themes

* chore(ui): allow bundle measurement variance

* fix(ui): preserve isolated leaf borders

* docs(ui): tighten shadow rollout guidance

* test(ui): remove redundant avatar story plays

* fix(ui): inherit provider theme in footnotes

* fix(ui): address shadow isolation review feedback

* feat(ui): isolate theme settings content (YPE-5994)

* refactor(ui): simplify theme settings evidence (YPE-5994)

* test(ui): close theme settings review gaps (YPE-5994)

* fix(ui): preserve constrained settings layout (YPE-5994)

* fix(ui): finalize settings host strategy (YPE-5994)

* fix(ui): address shadow layout review findings

* test(ui): make future reader boundary test falsifiable (YPE-5994)

Render the reader directly in the simulated boundary so the test fails
if the Toolbar stops reusing it for settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ui): preserve popover keyboard navigation across shadow roots

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants